#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <functional>
#include <memory>
#include <cstdio>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdlib>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <fstream>
#include <unordered_map>
#include <utility>
#include <signal.h>

namespace server {
    using namespace std;

    enum {
        SUCCESS,
        USAGE_ERR,
        SOCKET_ERR,
        BIND_ERR,
        OPEN_ERR
    };

    class UDPServer {
    private:
        using func_t = function<void(int, string, uint16_t, string)>;

    private:
        uint16_t _port; // 服务端端口号，16位两字节

        //* 但是对于服务器端，一般来说端口并不会指名的绑定一个ip。
        //* 如果明确的绑定了一个ip，那么该端口只会从服务器中对应ip处(如服务器的公网ip或私网ip，因为一个服务器可能会有多个ip，各个ip都有可能会接收到数据，如果明确指明了一个ip就不能从其他ip处接收消息了)接收数据；
        //* 但如果不明确绑定一个ip，那么端口就会获取发送至该服务器该端口处的全部数据。
        string _ip; // 端口号绑定的ip 

        int _sockfd; // 套接字fd
        func_t _callback; // 回调函数，用来处理接收到的消息

        static const string _default_ip; // 只有整形才能在定义时赋值
        const int _message_buffer_size = 1024;

    public:
        UDPServer(const func_t& handle, const uint16_t& port, const string& ip = _default_ip)
            : _port(port)
            , _ip(ip)
            , _sockfd(-1)
            , _callback(handle)
        {}

    public:
        static void usage(string process) {
            cout << "\nusage:\n\t" << process << " local_port\n\n";
        }

        void initServer() {
            //TODO: 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET为ipv4网络通信，SOCK_DGRAM为udp协议，0为不指明特定协议，根据前两个参数自己适配
            if (_sockfd == -1) {
                perror("socket err!");
                exit(SOCKET_ERR);
            }

            cout << "socket success! sockfd: " << _sockfd << endl;

            //TODO: socket绑定端口和IP
            //* 服务器特定的服务都需要一个特定的端口，该端口不能所以改变，所以必须绑定一个明确的端口
            //* 但是服务器可能存在多个IP，各个ip都可能会接收到发送到该端口的信息
            //* 所以一般来说不能绑定服务器中特定的一个IP，而应该用0这个默认ip来接收全部发送到该服务器的该端口的全部信息
            //* 除非你的业务就是需要接收发送到某个特定IP上的信息

            struct sockaddr_in local;
            bzero(&local, sizeof(local));
            local.sin_family = AF_INET; // 确定是网络通信还是unix域间通信
            local.sin_port = htons(_port); // 因为端口和ip也要进行网络通信发送给对方，所以要切换成网络字节序(大端)
            local.sin_addr.s_addr = inet_addr(_ip.c_str()); // inet_addr有两个功能，一是将字符串转为uint32_t，因为进行网络通信的ip需要为4字节型而不是字符串型；二是将4字节型ip转化为网络字节序
            //* local.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY == 0 绑定任意IP，服务器的真正写法

            int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
            if (n == -1) {
                cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
        }

        void start() {
            char buffer[_message_buffer_size];

            for(;;) {
                //todo 读取数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer); // len必须初始化为sizeof(peer)，他是输入输出型参数
                size_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len); // 0为阻塞时等待接收数据

                if (s > 0) {
                    buffer[s] = '\0';
                    string client_ip = inet_ntoa(peer.sin_addr); // 将网络字节序转为本机字节序 将4字节ip转为点分十进制ip
                    uint16_t client_port = ntohs(peer.sin_port);
                    string message(buffer);

                    cout << client_ip << "[" << client_port << "]# " << message << endl;
                    _callback(_sockfd, client_ip, client_port, message); // 对接收到的消息进行处理
                }
            }
        }

    }; //@end class UDPServer

    const string UDPServer::_default_ip = "0.0.0.0"; // 当socket绑定的ip为0时，默认接收发送到该服务器的该端口的全部数据，不限制ip

}//@end namespace server